/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* eggtrayicon.c
 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "config.h"
#include <string.h>
#include <libintl.h>

#include "eggtrayicon.h"

#include <gdkconfig.h>
#if defined (GDK_WINDOWING_X11)
	#include <gdk/gdkx.h>
	#include <X11/Xatom.h>
#elif defined (GDK_WINDOWING_WIN32)
	#include <gdk/gdkwin32.h>
#endif

#ifndef EGG_COMPILATION
	#ifndef _
		#define _(x) dgettext (GETTEXT_PACKAGE, x)
		#define N_(x) x
	#endif
#else
	#define _(x) x
	#define N_(x) x
#endif

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2

#define SYSTEM_TRAY_ORIENTATION_HORZ 0
#define SYSTEM_TRAY_ORIENTATION_VERT 1

enum
{
	PROP_0,
	PROP_ORIENTATION
};

static GtkPlugClass *parent_class = NULL;

static void egg_tray_icon_init (EggTrayIcon *icon);
static void egg_tray_icon_class_init (EggTrayIconClass *klass);

static void egg_tray_icon_get_property (GObject    *object,
										guint       prop_id,
										GValue     *value,
										GParamSpec *pspec);

static void egg_tray_icon_realize   (GtkWidget *widget);
static void egg_tray_icon_unrealize (GtkWidget *widget);

#ifdef GDK_WINDOWING_X11
static void egg_tray_icon_update_manager_window    (EggTrayIcon *icon,
													gboolean     dock_if_realized);
static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
#endif

GType
egg_tray_icon_get_type (void)
{
	static GType our_type = 0;

	if ( our_type == 0 )
	{
		static const GTypeInfo our_info =
		{
			sizeof (EggTrayIconClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) egg_tray_icon_class_init,
			NULL, /* class_finalize */
			NULL, /* class_data */
			sizeof (EggTrayIcon),
			0,	  /* n_preallocs */
			(GInstanceInitFunc) egg_tray_icon_init
		};

		our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
	}

	return our_type;
}

static void
egg_tray_icon_init (EggTrayIcon *icon)
{
	icon->stamp = 1;
	icon->orientation = GTK_ORIENTATION_HORIZONTAL;

	gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
}

static void
egg_tray_icon_class_init (EggTrayIconClass *klass)
{
	GObjectClass *gobject_class = (GObjectClass *)klass;
	GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;

	parent_class = g_type_class_peek_parent (klass);

	gobject_class->get_property = egg_tray_icon_get_property;

	widget_class->realize   = egg_tray_icon_realize;
	widget_class->unrealize = egg_tray_icon_unrealize;

	g_object_class_install_property (gobject_class,
									 PROP_ORIENTATION,
									 g_param_spec_enum ("orientation",
														_("Orientation"),
														_("The orientation of the tray."),
														GTK_TYPE_ORIENTATION,
														GTK_ORIENTATION_HORIZONTAL,
														G_PARAM_READABLE));

#if defined (GDK_WINDOWING_X11)
	/* Nothing */
#elif defined (GDK_WINDOWING_WIN32)
	g_warning ("Port eggtrayicon to Win32");
#else
	g_warning ("Port eggtrayicon to this GTK+ backend");
#endif
}

static void
egg_tray_icon_get_property (GObject    *object,
							guint       prop_id,
							GValue     *value,
							GParamSpec *pspec)
{
	EggTrayIcon *icon = EGG_TRAY_ICON (object);

	switch ( prop_id )
	{
		case PROP_ORIENTATION:
			g_value_set_enum (value, icon->orientation);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

#ifdef GDK_WINDOWING_X11

static void
egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
{
	Display *xdisplay;
	Atom type;
	int format;
	union
	{
		gulong *prop;
		guchar *prop_ch;
	} prop = { NULL};
	gulong nitems;
	gulong bytes_after;
	int error, result;

	g_assert (icon->manager_window != None);

	xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));

	gdk_error_trap_push ();
	type = None;
	result = XGetWindowProperty (xdisplay,
								 icon->manager_window,
								 icon->orientation_atom,
								 0, G_MAXLONG, FALSE,
								 XA_CARDINAL,
								 &type, &format, &nitems,
								 &bytes_after, &(prop.prop_ch));
	error = gdk_error_trap_pop ();

	if ( error || result != Success )
		return;

	if ( type == XA_CARDINAL )
	{
		GtkOrientation orientation;

		orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
					  GTK_ORIENTATION_HORIZONTAL :
					  GTK_ORIENTATION_VERTICAL;

		if ( icon->orientation != orientation )
		{
			icon->orientation = orientation;

			g_object_notify (G_OBJECT (icon), "orientation");
		}
	}

	if ( prop.prop )
		XFree (prop.prop);
}

static GdkFilterReturn
egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
{
	EggTrayIcon *icon = user_data;
	XEvent *xev = (XEvent *)xevent;

	if ( xev->xany.type == ClientMessage &&
		 xev->xclient.message_type == icon->manager_atom &&
		 xev->xclient.data.l[1] == icon->selection_atom )
	{
		egg_tray_icon_update_manager_window (icon, TRUE);
	} else if ( xev->xany.window == icon->manager_window )
	{
		if ( xev->xany.type == PropertyNotify &&
			 xev->xproperty.atom == icon->orientation_atom )
		{
			egg_tray_icon_get_orientation_property (icon);
		}
		if ( xev->xany.type == DestroyNotify )
		{
			egg_tray_icon_manager_window_destroyed (icon);
		}
	}
	return GDK_FILTER_CONTINUE;
}

#endif  

static void
egg_tray_icon_unrealize (GtkWidget *widget)
{
#ifdef GDK_WINDOWING_X11
	EggTrayIcon *icon = EGG_TRAY_ICON (widget);
	GdkWindow *root_window;

	if ( icon->manager_window != None )
	{
		GdkWindow *gdkwin;

		gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
												icon->manager_window);

		gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
	}

	root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));

	gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);

	if ( GTK_WIDGET_CLASS (parent_class)->unrealize )
		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
#endif
}

#ifdef GDK_WINDOWING_X11

static void
egg_tray_icon_send_manager_message (EggTrayIcon *icon,
									long         message,
									Window       window,
									long         data1,
									long         data2,
									long         data3)
{
	XClientMessageEvent ev;
	Display *display;

	ev.type = ClientMessage;
	ev.window = window;
	ev.message_type = icon->system_tray_opcode_atom;
	ev.format = 32;
	ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
	ev.data.l[1] = message;
	ev.data.l[2] = data1;
	ev.data.l[3] = data2;
	ev.data.l[4] = data3;

	display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));

	gdk_error_trap_push ();
	XSendEvent (display,
				icon->manager_window, False, NoEventMask, (XEvent *)&ev);
	XSync (display, False);
	gdk_error_trap_pop ();
}

static void
egg_tray_icon_send_dock_request (EggTrayIcon *icon)
{
	egg_tray_icon_send_manager_message (icon,
										SYSTEM_TRAY_REQUEST_DOCK,
										icon->manager_window,
										gtk_plug_get_id (GTK_PLUG (icon)),
										0, 0);
}

static void
egg_tray_icon_update_manager_window (EggTrayIcon *icon,
									 gboolean     dock_if_realized)
{
	Display *xdisplay;

	if ( icon->manager_window != None )
		return;

	xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));

	XGrabServer (xdisplay);

	icon->manager_window = XGetSelectionOwner (xdisplay,
											   icon->selection_atom);

	if ( icon->manager_window != None )
		XSelectInput (xdisplay,
					  icon->manager_window, StructureNotifyMask|PropertyChangeMask);

	XUngrabServer (xdisplay);
	XFlush (xdisplay);

	if ( icon->manager_window != None )
	{
		GdkWindow *gdkwin;

		gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
												icon->manager_window);

		gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);

		if ( dock_if_realized && GTK_WIDGET_REALIZED (icon) )
			egg_tray_icon_send_dock_request (icon);

		egg_tray_icon_get_orientation_property (icon);
	}
}

static void
egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
{
	GdkWindow *gdkwin;

	g_return_if_fail (icon->manager_window != None);

	gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
											icon->manager_window);

	gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);

	icon->manager_window = None;

	egg_tray_icon_update_manager_window (icon, TRUE);
}

#endif

static void
egg_tray_icon_realize (GtkWidget *widget)
{
#ifdef GDK_WINDOWING_X11
	EggTrayIcon *icon = EGG_TRAY_ICON (widget);
	GdkScreen *screen;
	GdkDisplay *display;
	Display *xdisplay;
	char buffer[256];
	GdkWindow *root_window;

	if ( GTK_WIDGET_CLASS (parent_class)->realize )
		GTK_WIDGET_CLASS (parent_class)->realize (widget);

	screen = gtk_widget_get_screen (widget);
	display = gdk_screen_get_display (screen);
	xdisplay = gdk_x11_display_get_xdisplay (display);

	/* Now see if there's a manager window around */
	g_snprintf (buffer, sizeof (buffer),
				"_NET_SYSTEM_TRAY_S%d",
				gdk_screen_get_number (screen));

	icon->selection_atom = XInternAtom (xdisplay, buffer, False);

	icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);

	icon->system_tray_opcode_atom = XInternAtom (xdisplay,
												 "_NET_SYSTEM_TRAY_OPCODE",
												 False);

	icon->orientation_atom = XInternAtom (xdisplay,
										  "_NET_SYSTEM_TRAY_ORIENTATION",
										  False);

	egg_tray_icon_update_manager_window (icon, FALSE);
	egg_tray_icon_send_dock_request (icon);

	root_window = gdk_screen_get_root_window (screen);

	/* Add a root window filter so that we get changes on MANAGER */
	gdk_window_add_filter (root_window,
						   egg_tray_icon_manager_filter, icon);
#endif
}

EggTrayIcon *
egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
{
	g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

	return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
}

EggTrayIcon*
egg_tray_icon_new (const gchar *name)
{
	return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
}

guint
egg_tray_icon_send_message (EggTrayIcon *icon,
							gint         timeout,
							const gchar *message,
							gint         len)
{
	guint stamp;

	g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
	g_return_val_if_fail (timeout >= 0, 0);
	g_return_val_if_fail (message != NULL, 0);

#ifdef GDK_WINDOWING_X11
	if ( icon->manager_window == None )
		return 0;
#endif

	if ( len < 0 )
		len = strlen (message);

	stamp = icon->stamp++;

#ifdef GDK_WINDOWING_X11
	/* Get ready to send the message */
	egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
										(Window)gtk_plug_get_id (GTK_PLUG (icon)),
										timeout, len, stamp);

	/* Now to send the actual message */
	gdk_error_trap_push ();
	while ( len > 0 )
	{
		XClientMessageEvent ev;
		Display *xdisplay;

		xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));

		ev.type = ClientMessage;
		ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
		ev.format = 8;
		ev.message_type = XInternAtom (xdisplay,
									   "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
		if ( len > 20 )
		{
			memcpy (&ev.data, message, 20);
			len -= 20;
			message += 20;
		} else
		{
			memcpy (&ev.data, message, len);
			len = 0;
		}

		XSendEvent (xdisplay,
					icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
		XSync (xdisplay, False);
	}
	gdk_error_trap_pop ();
#endif

	return stamp;
}

void
egg_tray_icon_cancel_message (EggTrayIcon *icon,
							  guint        id)
{
	g_return_if_fail (EGG_IS_TRAY_ICON (icon));
	g_return_if_fail (id > 0);
#ifdef GDK_WINDOWING_X11  
	egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
										(Window)gtk_plug_get_id (GTK_PLUG (icon)),
										id, 0, 0);
#endif
}

GtkOrientation
egg_tray_icon_get_orientation (EggTrayIcon *icon)
{
	g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);

	return icon->orientation;
}
